Saltar al contenido principal

7.1 Excepciones en Python

1️⃣ Qué es una excepción​

Una excepción es un evento que ocurre durante la ejecución e interrumpe el flujo normal del programa porque algo ha ido mal (o porque queremos señalar una situación especial).

Ejemplos típicos:

  • Dividir entre 0 → ZeroDivisionError
  • Convertir "hola" a int → ValueError
  • Acceder a una clave que no existe en un diccionario → KeyError
  • Abrir un archivo que no existe → FileNotFoundError
print(10 / 0)  # ZeroDivisionError

2️⃣ Modelo formal de excepciones​

En Python, las excepciones son objetos que representan condiciones anómalas detectadas en tiempo de ejecución.

El modelo sigue el patrón:

  1. Se detecta una condición excepcional.
  2. Se instancia un objeto excepción.
  3. Se lanza mediante raise.
  4. El intérprete busca un manejador compatible en la pila de llamadas.
  5. Si no lo encuentra → termina el programa.

Esto es un modelo basado en propagación por pila (stack unwinding).


3️⃣ Manejo básico de excepciones​

🟩 try / except​

La estructura más común:

try:
# código que puede fallar
...
except TipoDeError:
# qué hacer si falla con ese error
...

Ejemplo:

try:
n = int(input("Introduce un número: "))
print(10 / n)
except ValueError:
print("Eso no es un número válido.")
except ZeroDivisionError:
print("No se puede dividir entre 0.")

🟦 Capturar “cualquier excepción”​

Se puede, pero conviene hacerlo con cuidado:

try:
...
except Exception as e:
print("Ha ocurrido un error:", e)
Recomendación

Captura el tipo más específico posible (ValueError, KeyError, etc.) y usa Exception solo si de verdad te encaja.

🟨 as e: guardar el error​

Muy útil para mostrar o registrar el mensaje real:

try:
lista = [1, 2, 3]
print(lista[10])
except IndexError as e:
print("Índice fuera de rango:", e)

🟧 else: si NO hubo excepción​

else se ejecuta solo si el try termina sin errores.

try:
n = int(input("Número: "))
except ValueError:
print("No es un número.")
else:
print("Perfecto, tu número es:", n)

Cuándo usarlo: para separar claramente “lo que puede fallar” (try) de “lo que debe pasar si todo va bien” (else).

🟥 finally: pase lo que pase​

finally se ejecuta siempre, haya error o no. Se usa para limpieza: cerrar recursos, liberar cosas, etc.

f = None
try:
f = open("datos.txt", "r", encoding="utf-8")
contenido = f.read()
print(contenido)
except FileNotFoundError:
print("El archivo no existe.")
finally:
if f is not None:
f.close()
print("Archivo cerrado (si se llegó a abrir).")

4️⃣ Lanzar excepciones​

raise​

A veces el programa detecta un dato inválido y quiere detener la operación con un error controlado.

edad = -5
if edad < 0:
raise ValueError("La edad no puede ser negativa.")

🟩 Re-lanzar la excepción original​

Útil cuando quieres hacer algo (log, mensaje) y luego dejar que el error siga su camino:

try:
n = int("hola")
except ValueError:
print("Registro: hubo un error de conversión")
raise # vuelve a lanzar el mismo error

🟦 Excepciones personalizadas​

Cuando un error es “de tu dominio” (tu app, tu sistema), conviene crear una excepción propia.

class SaldoInsuficienteError(Exception):
pass

def retirar(saldo, cantidad):
if cantidad > saldo:
raise SaldoInsuficienteError("No hay saldo suficiente.")
return saldo - cantidad

try:
saldo = retirar(100, 200)
except SaldoInsuficienteError as e:
print("Operación rechazada:", e)

🟨 Con datos adicionales (mejorando la excepción)​

class SaldoInsuficienteError(Exception):
def __init__(self, saldo, cantidad):
self.saldo = saldo
self.cantidad = cantidad
super().__init__(f"Saldo {saldo} insuficiente para retirar {cantidad}.")

raise SaldoInsuficienteError(100, 200)

5️⃣ Jerarquía de excepciones​

Jerarquía de excepciones simplificada
BaseException
├── SystemExit
├── KeyboardInterrupt
└── Exception
├── ArithmeticError
├── LookupError
├── ValueError
├── TypeError
├── OSError
└── ...
Consejo clave

Si capturas Exception, también capturas casi todo. Si capturas una excepción específica, no “tapas” errores inesperados.

peligro

⚠️ Evita capturar BaseException (incluye cosas como KeyboardInterrupt, SystemExit).


6️⃣ Excepciones frecuentes​

  • ValueError: valor inválido (ej. int("hola"))
  • TypeError: tipo incorrecto (ej. "3" + 2)
  • KeyError: clave inexistente en dict
  • IndexError: índice fuera de rango en lista/tupla
  • FileNotFoundError: archivo no encontrado
  • PermissionError: sin permisos
  • AttributeError: atributo inexistente
  • ImportError / ModuleNotFoundError: importaciones
  • ZeroDivisionError: división por cero

Ejemplo rápido de KeyError:

d = {"a": 1}
try:
print(d["b"])
except KeyError:
print("Esa clave no existe.")

7️⃣ Ejemplos completos típicos​

🟩 Pedir número repetidamente hasta que sea correcto​

while True:
try:
n = int(input("Número entero: "))
break
except ValueError:
print("Introduce un entero válido.")
print("OK:", n)

🟦 Función robusta con validación + raise​

def calcular_media(lista):
if not lista:
raise ValueError("La lista no puede estar vacía.")
return sum(lista) / len(lista)

try:
print(calcular_media([]))
except ValueError as e:
print("Error:", e)

🟨 Varias excepciones en un solo except​

try:
n = int(input("n: "))
print(10 / n)
except (ValueError, ZeroDivisionError) as e:
print("Entrada inválida o división por 0:", e)

✅ Buenas prácticas en el uso de excepciones​

buenas prácticas
  • Utilizar excepciones únicamente para situaciones excepcionales Las excepciones deben emplearse para condiciones anómalas o estados inválidos, no como sustituto de estructuras de control normales (if, for, etc.).

  • Capturar la excepción más específica posible Se recomienda interceptar únicamente los tipos de error que realmente se pueden gestionar. Evitar capturas genéricas como except Exception salvo que exista una justificación clara (por ejemplo, en una capa superior de la aplicación).

  • No silenciar errores deliberadamente Evitar construcciones como:

    except:
    pass

    Este patrón oculta fallos reales y dificulta la depuración y el mantenimiento del sistema.

  • Proporcionar mensajes claros y significativos al lanzar excepciones Cuando se utiliza raise, el mensaje asociado debe describir con precisión el problema y aportar contexto suficiente para facilitar la diagnosis.

  • Aplicar el principio EAFP (Easier to Ask Forgiveness than Permission) En Python es idiomático intentar la operación y capturar la excepción si ocurre, en lugar de verificar previamente todas las condiciones posibles. Esto produce código más limpio y reduce verificaciones redundantes.

  • Permitir la propagación cuando no se puede gestionar el error localmente Si una función no puede resolver adecuadamente la excepción, debe dejarla propagarse hacia una capa superior en lugar de capturarla innecesariamente.

  • Preservar el traceback cuando se encapsulan excepciones Si se transforma una excepción técnica en una excepción de dominio, se recomienda utilizar:

    raise NuevaExcepcion(...) from e

    Esto mantiene la traza original y facilita el análisis posterior.

  • Utilizar herramientas de logging en lugar de print en entornos profesionales En aplicaciones reales, los errores deben registrarse mediante sistemas de logging que conserven el traceback completo.

  • Consultar o mostrar el traceback cuando sea necesario para depuración Durante el desarrollo, puede utilizarse:

    import traceback
    traceback.print_exc()

    para obtener información detallada sobre la pila de llamadas.